# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
"""
Game mix-ins class for drag move support. Handles moving cards between piles.
"""
__docformat__ = 'restructuredtext en'

import logging

Logger = logging.getLogger('PySolFC.GameLib.DragMixin')

from ..settings import DEBUG


class GameDrag(object):
    """
    Game drag mix-ins class.

    Provide methods moving cards between piles for Game.

    *Variables*:
      __regions : {dict}
        Mapping to determine stack regions for when cards move between piles.
    """
    images = canvas = allstacks = None
    sg = {}

    def __init__(self):
        """
        __init__() -> None

         Initialize drag mix-in subclass then call next ancestor.

        *Variables*
          __regions : {dict}
            Create empty map keys.
              - info : {seq(seq(seq(Stack ...), seq(int, int, int, int)) ...)}
                Group of piles and its bounding recting coords.
              - remaining : {seq(Stack ...)}
                Group of piles in no region.
              - data : {seq(seq(int, int, **info**) ...)}
                Appended by _set_region() and accessed by _optimize_regions().
              - init_info : {**info**}.
        """
        self.__regions = {'info': [], 'remaining': [], 'data': [],
                          'init_info': []}
        super(GameDrag, self).__init__()

    def _view_resize(self):
        """
        _view_resize() -> None

        Resize game regions.

        *Variables*:
          self.__regions : {dict}
            - info : {seq(seq(seq(Stack ...), seq(int, int, int, int)) ...)}
              resize bounding rectangle coords with current scale.
        """
        scale = self._view_scale
        self.__regions['info'] = tuple((s, (r._scale(scale)),)
                                        for p, r in self.__regions['init_info'])

        ancestor = super(GameDrag, self)
        if hasattr(ancestor, '_view_resize'):
            ancestor._view_resize()

    def _closest_stack(self, card, stacks, dragstack):
        """
        _closest_stack(card:Card, stacks:seq(Stack ...), dragstack:Stack)
          -> Stack

        Compare stacks for closest stack to card. Optimized to locate stacks
        with only a single or no offset. Return closest stack to coords.
        Method maybe subclass to modify closest stack determination.
        """
        closest, cdist = None, 999999999
        for stack in stacks:
            x, y = card.coord
            dist = (stack.x - x) ** 2 + (stack.y - y) ** 2
            if dist < cdist:
                closest, cdist = stack, dist
        return closest

    def get_closest_stack(self, card, dragstack):
        """
        get_closest_stack(card:Card, dragstack:Stack) -> Stack

        Compare card for closest region of stack. Called within the mouse motion
        handler event to locate current stack under mouse to find stack that
        could build with moving stack. Return closest stack to coords. Subclass
        _closest_stack method to change closest stack determination.
        """
        regions = self.__regions
        for stacks, rect in regions['info']:
            x, y = card.coord
            if rect[0] <= x <= rect[2] and rect[1] <= y <= rect[3]:
                return self._closest_stack(card, stacks, dragstack)
        return self._closest_stack(card, regions['remaining'], dragstack)

    def _set_region(self, stacks, rect, priority=0):
        """
        _set_region(self, stacks, rect, priority=0) -> None

        Define stack regions. Note _optimize_regions() needs to be called before
        accessed by get_closest_stack().

        *Variables*
          self.__regions : {dict}
            - **data**: {seq(seq(int, int, **info**) ...)}
              Append region data for use by _optimize_regions().

        *Raises*:
          - Assertion Error when stack not in self.allstack list.
          - Assertion Error when stack outside region rect.
          - Assertion Error when stack in multiple regions and same prioity.
        """
        from ..utillib.misc import Block
        assert len(stacks) > 0 and len(rect) == 4
        rect = Block(*rect)
        Logger.debug('set region rectangle: (%s, %s, %s, %s)', *rect)
        assert rect.l < rect.r and rect.t < rect.b
        if DEBUG:
            from ..viewlib.canvas import RectangleItem
            bbox = Block._scale(self._view_scale)
            RectangleItem(self.canvas, bbox, width=2, fill=None, outline='red')

        # verify stacks are within the region rectangle
        for stack in stacks:
            Logger.debug('  stack: (%s)', stack)
            assert stack and stack in self.allstacks
            xcoord, ycoord = stack.init_coord

            # verify stack not in another region with the same priority
            Logger.debug('  stack coord: (%s, %s)', xcoord, ycoord)
            assert rect.l <= xcoord <= rect.r and rect.t <= ycoord <= rect.b
            for data in self.__regions['data']:
                assert priority != data[0] or stack not in data[2]

        # add to regions
        self.__regions['data'].append((priority, -len(self.__regions['data']),
                                       tuple(stacks), rect,))

    def _optimize_regions(self):
        """
        _optimize_regions(self) -> None

        Optimize regions data by priority and size. Data set accesed by
        get_closest_stack().

        *Variables*
          self.__regions : {dict}
              Set mapping from data collected by _set_region().
              - info : {seq(seq(seq(Stack ...), seq(int, int, int, int)) ...)}
                Set group of piles and its bounding recting coords from **data**
                by priority and count.
              - remaining : {seq(Stack ...)}
                Group of piles from openstacks not in any region.
              - init_info : {**info**}.
                Set to value on **info**.
        """
        regions = self.__regions
        regions['data'].sort()
        regions['data'].reverse()

        # copy (stacks, rect) from data to regions.info
        regions['init_info'] = tuple(d[2:] for d in regions['data'])
        regions['info'] = regions['init_info']

        # determine remaining stacks
        remaining = list(self.sg['openstacks'])

        for stack in (s[0] for s in regions['init_info']):
            while stack in remaining:
                remaining.remove(stack)
        regions['remaining'] = tuple(remaining)